﻿using System;
using System.Linq;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;

using LeanEngine.Entity;
using System.Threading.Tasks;
using System.Data.SqlClient;

namespace LeanEngine.Data.SqlClient
{
    internal sealed class SqlClientBulkInsertCommand : IDisposable
    {
        #region Declaration Members

        private MetaTable _metaTable;
        private DataTable _table;
        private IList _dtoList;

        #endregion

        #region IBatchCommand Members

        public void Execute<T>(SqlConnection connection, SqlTransaction transaction, IList<T> list, int timeOut)
            where T : PlainEntity
        {
            _dtoList = (IList)list;
            _metaTable = MetaTableCache.GetItem(typeof(T));
            _table = new DataTable(_metaTable.TableName);
            foreach (var item in _metaTable.ColumnsList.WhereNotDbGenerated())
            {
                if (item.IsEntity)
                {
                    var fkMetaTable = MetaTableCache.GetItem(item.DataType);
                    foreach (var fkItem in fkMetaTable.ColumnsList.WherePrimaryKey())
                    {
                        _table.Columns.Add(new DataColumn(fkItem.DBColumn.Name, fkItem.DataType));
                    }
                }
                else
                {
                    _table.Columns.Add(new DataColumn(item.DBColumn.Name, item.DataType));
                }
            }

            for (int i = 0; i < list.Count; i++)
            {
                _table.Rows.Add(_table.NewRow());
            }

            Parallel.For(0, list.Count, index =>
            {
                ParseEntityToDataRow<T>(index);
            });

            ExecuteBulkCopy<T>(_table, timeOut, connection, transaction);
        }

        #endregion

        #region Method Members

        private void ExecuteBulkCopy<T>(DataTable table, int timeOut, SqlConnection connection, SqlTransaction sqlTrans)
            where T : PlainEntity
        {
            using (SqlBulkCopy bcp = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, sqlTrans))
            {
                bcp.ColumnMappings.Clear();
                foreach(var item in _metaTable.ColumnsList.WhereNotDbGenerated())
                {
                    bcp.ColumnMappings.Add(item.DBColumn.Name, item.DBColumn.Name);
                }

                bcp.DestinationTableName = _table.TableName;
                bcp.BatchSize = table.Rows.Count;
                bcp.BulkCopyTimeout = timeOut;

                bcp.WriteToServer(table);
            }
        }

        private void ParseEntityToDataRow<T>(int rowIndex)
            where T : PlainEntity
        {
            T dto = (T)_dtoList[rowIndex];
            DataRow row = _table.Rows[rowIndex];

            lock (_table.Rows.SyncRoot)
            {
                int index = 0;
                foreach (var item in _metaTable.ColumnsList.WhereNotDbGenerated())
                {
                    if (item.IsEntity)
                    {
                        var refDtovalue = item.GetValue(dto);
                        var fkMetaTable = MetaTableCache.GetItem(item.DataType);
                        foreach (var fkItem in fkMetaTable.ColumnsList.WherePrimaryKey())
                        {
                            row[index++] = fkItem.GetValue(refDtovalue) ?? DBNull.Value;
                        }
                    }
                    else
                    {
                        row[index++] = item.GetValue(dto) ?? DBNull.Value;
                    }
                }
            }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Release all resources used
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Release all resources used
        /// </summary>
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this._table != null)
                {
                    this._table.Rows.Clear();
                    this._table.Columns.Clear();
                    this._table.Clear();
                    this._table.Dispose();
                    this._table = null;
                }
            }
        }

        #endregion
    }
}
